#ifndef _LOG_FILE_H_
#error "Please include <clog.h> first"
#else
#ifndef _EXCEPTION_HANDLER_H_
#define _EXCEPTION_HANDLER_H_

#include <imagehlp.h>
#pragma comment(lib, "dbghelp.lib") 

#define STACKWALK_MAX_NAMELEN 1024

class CExceptionHandler
{
private:
    CExceptionHandler()
    {
        m_prevFilter = SetUnhandledExceptionFilter(UnhandledExceptionFilter);
    }
    ~CExceptionHandler()
    {
        SetUnhandledExceptionFilter(m_prevFilter);
    }
    static LONG WINAPI UnhandledExceptionFilter(PEXCEPTION_POINTERS pExceptionInfo)
    {
        _gExpHandler.GenerateExceptionReport(pExceptionInfo);
        _gExpHandler.GenerateMiniDump(pExceptionInfo);

        if (NULL != _gExpHandler.m_prevFilter)
            return _gExpHandler.m_prevFilter(pExceptionInfo);
        else
            return EXCEPTION_CONTINUE_SEARCH;
    }
    // GenerateExceptionReport Function
    void GenerateExceptionReport(PEXCEPTION_POINTERS pExceptionInfo)
    {
        PEXCEPTION_RECORD pRecord = pExceptionInfo->ExceptionRecord;
        LOG_INFO("ExceptionCode=[0x%.8X] FaultAddress=[0x%.8X]",
            pRecord->ExceptionCode, pRecord->ExceptionAddress);
        PCONTEXT pCtx = pExceptionInfo->ContextRecord;
        HANDLE hProcess = GetCurrentProcess();
        HANDLE hThread = GetCurrentThread();

        if (!SymInitialize(hProcess, NULL, TRUE))
        {
            LOG_INFO("SymInitialize Error [%d]", GetLastError());
            return;
        }

        SymSetOptions(SymGetOptions() |
            SYMOPT_LOAD_LINES |
            SYMOPT_FAIL_CRITICAL_ERRORS);

        PIMAGEHLP_SYMBOL pSym = (PIMAGEHLP_SYMBOL)HeapAlloc(GetProcessHeap(),
            HEAP_ZERO_MEMORY,
            sizeof(IMAGEHLP_SYMBOL)+STACKWALK_MAX_NAMELEN);
        pSym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL);
        pSym->MaxNameLength = STACKWALK_MAX_NAMELEN;
        DWORD offsetFromLine = 0;
        // init STACKFRAME for first call
        STACKFRAME s = { 0 }; // in/out stackframe
        DWORD imageType = InitStackFrame(&s, pCtx);
        CHAR undName[STACKWALK_MAX_NAMELEN] = { 0 };
        IMAGEHLP_LINE Line = { sizeof(IMAGEHLP_LINE) };

        while (StackWalk(imageType, hProcess, hThread, &s, pCtx, NULL, NULL, NULL, NULL))
        {
            if (s.AddrPC.Offset == s.AddrReturn.Offset)
            {
                LOG_INFO("Endless [0x%.8X]", s.AddrPC.Offset);
                break;
            }
            if (0 != s.AddrPC.Offset)
            {
                if (SymGetSymFromAddr(hProcess, s.AddrPC.Offset, NULL, pSym))
                {
                    if (0 != UnDecorateSymbolName(pSym->Name, undName, STACKWALK_MAX_NAMELEN, UNDNAME_COMPLETE) &&
                        SymGetLineFromAddr(hProcess, s.AddrPC.Offset, &offsetFromLine, &Line))
                    {
                        LOG_INFO("[%S] (%S:%d)", undName, Line.FileName, Line.LineNumber);
                    }
                }
                else
                {
                    LOG_INFO("SymGetSymFromAddr Error [%d] Offset [0x%.8X]", GetLastError(), s.AddrPC.Offset);
                }
            }
            if (0 == s.AddrReturn.Offset)
            {
                SetLastError(ERROR_SUCCESS);
                break;
            }
        }
        HeapFree(GetProcessHeap(), 0, pSym);

        SymCleanup(hProcess);
    }
    DWORD InitStackFrame(LPSTACKFRAME pFrame, PCONTEXT pCtx)
    {
#ifdef _M_IX86
        // normally, call ImageNtHeader() and use machine info from PE header
        DWORD imageType = IMAGE_FILE_MACHINE_I386;
        pFrame->AddrPC.Offset = pCtx->Eip;
        pFrame->AddrPC.Mode = AddrModeFlat;
        pFrame->AddrFrame.Offset = pCtx->Ebp;
        pFrame->AddrFrame.Mode = AddrModeFlat;
        pFrame->AddrStack.Offset = pCtx->Esp;
        pFrame->AddrStack.Mode = AddrModeFlat;
#elif _M_X64
        DWORD imageType = IMAGE_FILE_MACHINE_AMD64;
        pFrame->AddrPC.Offset = pCtx->Rip;
        pFrame->AddrPC.Mode = AddrModeFlat;
        pFrame->AddrFrame.Offset = pCtx->Rsp;
        pFrame->AddrFrame.Mode = AddrModeFlat;
        pFrame->AddrStack.Offset = pCtx->Rsp;
        pFrame->AddrStack.Mode = AddrModeFlat;
#elif _M_IA64
        DWORD imageType = IMAGE_FILE_MACHINE_IA64;
        pFrame->AddrPC.Offset = pCtx->StIIP;
        pFrame->AddrPC.Mode = AddrModeFlat;
        pFrame->AddrFrame.Offset = pCtx->IntSp;
        pFrame->AddrFrame.Mode = AddrModeFlat;
        pFrame->AddrBStore.Offset = pCtx->RsBSP;
        pFrame->AddrBStore.Mode = AddrModeFlat;
        pFrame->AddrStack.Offset = pCtx->IntSp;
        pFrame->AddrStack.Mode = AddrModeFlat;
#else
#error "Platform not supported!"
#endif
        return imageType;
    }
    // GenerateMiniDump Function
    static BOOL WINAPI MiniDumpCallback(PVOID pParam,
        PMINIDUMP_CALLBACK_INPUT pInput,
        PMINIDUMP_CALLBACK_OUTPUT pOutput)
    {
        if (NULL == pInput || NULL == pOutput) return FALSE;
        switch (pInput->CallbackType)
        {
        case ModuleCallback:
            if (pOutput->ModuleWriteFlags & ModuleWriteDataSeg)
            if (!IsDataSectionNeeded(pInput->Module.FullPath))
                pOutput->ModuleWriteFlags &= (~ModuleWriteDataSeg);
        case IncludeModuleCallback:
        case IncludeThreadCallback:
        case ThreadCallback:
        case ThreadExCallback:
            return TRUE;
        default:
            return FALSE;
        }
    }
    static bool IsDataSectionNeeded(const WCHAR* pModuleName)
    {
        if (NULL == pModuleName) return false;
        WCHAR szFileName[MAX_PATH] = { 0 };
        _wsplitpath(pModuleName, NULL, NULL, szFileName, NULL);
        return _wcsicmp(szFileName, L"ntdll") == 0;
    }
    void GenerateMiniDump(PEXCEPTION_POINTERS pExceptionInfo)
    {
        HANDLE hFile = INVALID_HANDLE_VALUE;
        SYSTEMTIME tm;
        GetLocalTime(&tm);
        TCHAR szName[MAX_PATH] = { 0 };
        TCHAR szFile[MAX_PATH] = { 0 };
        GetModuleFileName(NULL, szFile, MAX_PATH);
        _tsplitpath(szFile, NULL, NULL, szName, NULL);
        do
        {
            switch (GetLastError())
            {
            case ERROR_SUCCESS:
                GetCurrentDirectory(MAX_PATH, szFile);
                break;
            case ERROR_ACCESS_DENIED:
                GetTempPath(MAX_PATH, szFile);
                break;
            default:
                return;
            }
            _stprintf(szFile,
                TEXT("%s\\%s_%.2d-%.2d-%.2d[%.2d-%.2d-%.2d-%.3d].dmp"),
                szFile, szName,
                tm.wYear, tm.wMonth, tm.wDay,
                tm.wMinute, tm.wSecond, tm.wMilliseconds);

            hFile = CreateFile(szFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
        } while (INVALID_HANDLE_VALUE == hFile);
        
        MINIDUMP_CALLBACK_INFORMATION mci = { (MINIDUMP_CALLBACK_ROUTINE)MiniDumpCallback, NULL };
        MINIDUMP_EXCEPTION_INFORMATION ExInfo = { GetCurrentThreadId(), pExceptionInfo, FALSE };
        MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, MiniDumpWithDataSegs, &ExInfo, NULL, &mci);
        
        CloseHandle(hFile);
    }
private:
    LPTOP_LEVEL_EXCEPTION_FILTER m_prevFilter;

    static CExceptionHandler _gExpHandler;
};

CExceptionHandler CExceptionHandler::_gExpHandler;

#endif // _EXCEPTION_HANDLER_H_

#endif // !_LOG_FILE_H_